package container

import (
	"github.com/sirupsen/logrus"
	"mydocker/common"
	"mydocker/utils"
	"os"
	"os/exec"
	"path"
	"syscall"
)

// NewParentProcess 创建一个会隔离 namespace 进程的 command
/*
这里是父进程，也就是当前进程执行的内容。
1.这里的 /proc/self/exe 调用中，/proc/self 指的是当前运行进程自己的环境，exec 其实就是自己调用了自己，使用这种方式对创建出来的进程进行初始化
2.后面的 args 是参数，其中 init 是传递给本进程的第一个参数，在本例中，其实就是会去调用 initCommand 去初始化进程的一些环境和资源
3.下面的 clone 参数就是去 fork 出来一个新进程，并使用了 namespace 隔离新创建的进程和外部环境。
4.如果用户指定了 -it 参数，就需要把当前进程的输入输出导入到标准输入输出上
*/
func NewParentProcess(tty bool, volume, containerID, imageName string, envs []string) (*exec.Cmd, *os.File) {
	// 创建一个匿名管道，用于传递参数
	// 将 readPipe 作为子进程的 ExtraFiles，子进程从 readPipe 中读取参数
	// 父进程中则通过 writePipe 将参数写入管道
	readPipe, writePipe, err := os.Pipe() // 返回一对关联的文件对象，从 r 的读取将返回写入 w 的数据
	if err != nil {
		logrus.Errorf("New pipe error %v", err)
		return nil, nil
	}
	// 调用自身，传入 init 参数，也就是执行 initCommand
	cmd := exec.Command("/proc/self/exe", "init")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}
	if tty { // 如果在前台运行，则将标准输入、输出和错误重定向到当前进程的对应标准流
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	} else {
		if _, err = os.Stat(common.DefaultContainerInfoPath); err != nil && os.IsNotExist(err) {
			if err = os.Mkdir(common.DefaultContainerInfoPath, common.Perm0777); err != nil {
				logrus.Errorf("mkdir container dir: %s, err: %v", common.DefaultContainerInfoPath, err)
				return nil, nil
			}
		}
		// 对于后台运行容器，将 stout、stderr 重定向到日志文件中，便于后续查看
		logDir := path.Join(common.DefaultContainerInfoPath, containerID)
		if _, err = os.Stat(logDir); err != nil && os.IsNotExist(err) {
			if err = os.Mkdir(logDir, common.Perm0622); err != nil {
				logrus.Errorf("NewParentProcess mkdir %s error: %v", logDir, err)
				return nil, nil
			}
		}
		logFilePath := path.Join(logDir, GetLogFile(containerID))
		file, err := os.Create(logFilePath)
		if err != nil {
			logrus.Errorf("NewParentProcess create log file: %s, err: %v", logFilePath, err)
			return nil, nil
		}
		cmd.Stdout = file
		cmd.Stderr = file
	}

	// 设置环境变量
	cmd.Env = append(cmd.Env, envs...)
	// 设置额外文件句柄
	cmd.ExtraFiles = []*os.File{
		readPipe, // 将读管道作为文件列表的一部分，供子进程使用
	}
	// 创建容器运行时目录
	err = NewWorkSpace(volume, containerID, imageName)
	if err != nil {
		logrus.Errorf("new workspace err: %v", err)
		return nil, nil
	}
	// 指定容器初始化后的工作目录
	cmd.Dir = utils.GetMerged(containerID)
	return cmd, writePipe // 返回创建的命令和写管道，子进程将使用写管道发送信息回父进程
}
